package moxl.relaxng.schema;

import java.util.List;

import moxl.dom.MoxlAttributeMap;
import moxl.dom.MoxlCharacterData;
import moxl.dom.MoxlElement;
import moxl.dom.MoxlNode;
import moxl.dom.QualifiedName;
import moxl.dom.XmlNamespaceContext;
import moxl.relaxng.schema.patterns.AfterPattern;
import moxl.relaxng.schema.patterns.AttributePattern;
import moxl.relaxng.schema.patterns.ChoicePattern;
import moxl.relaxng.schema.patterns.DataExceptPattern;
import moxl.relaxng.schema.patterns.DataPattern;
import moxl.relaxng.schema.patterns.ElementPattern;
import moxl.relaxng.schema.patterns.EmptyPattern;
import moxl.relaxng.schema.patterns.GroupPattern;
import moxl.relaxng.schema.patterns.InterleavePattern;
import moxl.relaxng.schema.patterns.ListPattern;
import moxl.relaxng.schema.patterns.NotAllowedPattern;
import moxl.relaxng.schema.patterns.OneOrMorePattern;
import moxl.relaxng.schema.patterns.TextPattern;
import moxl.relaxng.schema.patterns.ValuePattern;

public class ChildDerivativeComputer
{

	public Pattern childDeriv(XmlNamespaceContext cx, Pattern p, MoxlNode s)
	{
		if (s instanceof MoxlElement)
		{
			return childDeriv(cx, p, (MoxlElement) s);
		}
		else if (s instanceof MoxlCharacterData)
		{
			return textDeriv(cx, p, (MoxlCharacterData) s);
		}
		else
		{
			throw new IllegalStateException();
		}
	}

	private Pattern textDeriv(XmlNamespaceContext cx, Pattern p, MoxlCharacterData s)
	{
		if (p instanceof ChoicePattern)
		{
			Pattern p1 = ((ChoicePattern) p).getFirstPattern();
			Pattern p2 = ((ChoicePattern) p).getSecondPattern();
			return new ChoicePattern(textDeriv(cx, p1, s), textDeriv(cx, p2, s));
		}
		else if (p instanceof InterleavePattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof GroupPattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof AfterPattern)
		{
			Pattern p1 = ((AfterPattern) p).getFirstPattern();
			Pattern p2 = ((AfterPattern) p).getSecondPattern();
			return new AfterPattern(textDeriv(cx, p1, s), p2);
		}
		else if (p instanceof OneOrMorePattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof TextPattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof ValuePattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof DataPattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof DataExceptPattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof ListPattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else
		{
			return new NotAllowedPattern();
		}
	}

	public Pattern childDeriv(XmlNamespaceContext context, Pattern pattern, MoxlElement node)
	{
		Pattern p1 = startTagOpenDeriv(pattern, node.getName());
		Pattern p2 = attsDeriv(context, p1, node.getAttributeMap());
		Pattern p3 = startTagCloseDeriv(p2);
		Pattern p4 = childrenDeriv(context, p3, node.getChildren());
		return endTagDeriv(p4);
	}

	private Pattern startTagOpenDeriv(Pattern p, QualifiedName qn)
	{
		if (p instanceof ChoicePattern)
		{
			Pattern p1 = ((ChoicePattern) p).getFirstPattern();
			Pattern p2 = ((ChoicePattern) p).getSecondPattern();
			return new ChoicePattern(startTagOpenDeriv(p1, qn), startTagOpenDeriv(p2, qn));
		}
		else if (p instanceof ElementPattern)
		{
			ElementPattern element = (ElementPattern) p;
			if (element.getNameClass().contains(qn))
			{
				return new AfterPattern(element.getPattern(), new EmptyPattern());
			}
			else
			{
				return new NotAllowedPattern();
			}
		}
		else if (p instanceof InterleavePattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof GroupPattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof AfterPattern)
		{
			Pattern p1 = ((AfterPattern) p).getFirstPattern();
			Pattern p2 = ((AfterPattern) p).getSecondPattern();
			return applyAfter(new FlipFunction(p2), startTagOpenDeriv(p1, qn));
		}
		else
		{
			return new NotAllowedPattern();
		}
	}

	public Pattern applyAfter(OneArgFunction<Pattern, Pattern> f, Pattern p)
	{
		if (p instanceof AfterPattern)
		{
			Pattern p1 = ((AfterPattern) p).getFirstPattern();
			Pattern p2 = ((AfterPattern) p).getSecondPattern();
			return new AfterPattern(p1, f.execute(p2));
		}
		else if (p instanceof ChoicePattern)
		{
			Pattern p1 = ((ChoicePattern) p).getFirstPattern();
			Pattern p2 = ((ChoicePattern) p).getSecondPattern();
			return new ChoicePattern(applyAfter(f, p1), applyAfter(f, p2));
		}
		else if (p instanceof NotAllowedPattern)
		{
			return p;
		}
		else
		{
			throw new IllegalStateException("After pattern cannot occur here");
		}
	}

	private Pattern attsDeriv(XmlNamespaceContext context, Pattern p, MoxlAttributeMap attributes)
	{
		if (attributes.isEmpty())
		{
			return p;
		}
		else
		{
			throw new UnsupportedOperationException("TODO");
		}
	}

	private Pattern startTagCloseDeriv(Pattern p)
	{
		if (p instanceof AfterPattern)
		{
			AfterPattern ap = (AfterPattern) p;
			return new AfterPattern(startTagCloseDeriv(ap.getFirstPattern()), ap.getSecondPattern());
		}
		else if (p instanceof ChoicePattern)
		{
			ChoicePattern cp = (ChoicePattern) p;
			return new ChoicePattern(startTagCloseDeriv(cp.getFirstPattern()), startTagCloseDeriv(cp.getSecondPattern()));
		}
		else if (p instanceof GroupPattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof InterleavePattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof OneOrMorePattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else if (p instanceof AttributePattern)
		{
			throw new UnsupportedOperationException("TODO");
		}
		else
		{
			return p;
		}
	}

	private Pattern childrenDeriv(XmlNamespaceContext cx, Pattern p, List<MoxlNode> children)
	{
		if (children.isEmpty())
		{
			Pattern p1 = childDeriv(cx, p, EmptyStringCharacterData.getInstance());
			return new ChoicePattern(p, p1);
		}
		else if (children.size() == 1 && children.get(0) instanceof MoxlCharacterData)
		{
			MoxlCharacterData s = (MoxlCharacterData) children.get(0);
			Pattern p1 = childDeriv(cx, p, s);
			if (s.isWhitespace())
			{
				return new ChoicePattern(p, p1);
			}
			else
			{
				return p1;
			}
		}
		else
		{
			return stripChildrenDeriv(cx, p, children);
		}
	}

	private Pattern stripChildrenDeriv(XmlNamespaceContext cx, Pattern p, List<MoxlNode> children)
	{
		if (children.isEmpty())
		{
			return p;
		}
		else
		{
			MoxlNode h = children.get(0);
			List<MoxlNode> t = children.subList(1, children.size());
			return stripChildrenDeriv(cx, strip(h) ? p : childDeriv(cx, p, h), t);
		}
	}

	private boolean strip(MoxlNode s)
	{
		if (s instanceof MoxlCharacterData)
		{
			return whitespace((MoxlCharacterData) s);
		}
		return false;
	}

	private boolean whitespace(MoxlCharacterData s)
	{
		return s.isWhitespace();
	}

	private Pattern endTagDeriv(Pattern p)
	{
		if (p instanceof ChoicePattern)
		{
			Pattern p1 = ((ChoicePattern) p).getFirstPattern();
			Pattern p2 = ((ChoicePattern) p).getSecondPattern();
			return new ChoicePattern(endTagDeriv(p1), endTagDeriv(p2));
		}
		else if (p instanceof AfterPattern)
		{
			Pattern p1 = ((AfterPattern) p).getFirstPattern();
			Pattern p2 = ((AfterPattern) p).getSecondPattern();
			if (p1.nullable())
			{
				return p2;
			}
			else
			{
				return new NotAllowedPattern();
			}
		}
		else
		{
			return new NotAllowedPattern();
		}
	}
}
